home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-19 / rcs55.zip / PARTIME.C < prev    next >
C/C++ Source or Header  |  1991-09-15  |  20KB  |  634 lines

  1. /*
  2.  * PARTIME        parse date/time string into a TM structure
  3.  *
  4.  * Returns:
  5.  *    0 if parsing failed
  6.  *    else time values in specified TM structure and zone (unspecified values
  7.  *        set to TMNULL)
  8.  * Notes:
  9.  *    This code is quasi-public; it may be used freely in like software.
  10.  *    It is not to be sold, nor used in licensed software without
  11.  *    permission of the author.
  12.  *    For everyone's benefit, please report bugs and improvements!
  13.  *     Copyright 1980 by Ken Harrenstien, SRI International.
  14.  *    (ARPANET: KLH @ SRI)
  15.  */
  16.  
  17. /* Hacknotes:
  18.  *    If parsing changed so that no backup needed, could perhaps modify
  19.  *        to use a FILE input stream.  Need terminator, though.
  20.  *    Perhaps should return 0 on success, else a non-zero error val?
  21.  */
  22.  
  23. /* $Log: partime.c%v $
  24.  * Revision 1.2  1991/08/23  13:31:42  SGP
  25.  * Ported to MSDOS using Borland C++
  26.  *
  27.  * Revision 5.4  1990/10/04  06:30:15  eggert
  28.  * Remove date vs time heuristics that fail between 2000 and 2400.
  29.  * Check for overflow when lexing an integer.
  30.  * Parse 'Jan 10 LT' as 'Jan 10, LT', not 'Jan, 10 LT'.
  31.  *
  32.  * Revision 5.3  1990/09/24  18:56:31  eggert
  33.  * Update timezones.
  34.  *
  35.  * Revision 5.2  1990/09/04  08:02:16  eggert
  36.  * Don't parse two-digit years, because it won't work after 1999/12/31.
  37.  * Don't permit 'Aug Aug'.
  38.  *
  39.  * Revision 5.1  1990/08/29  07:13:49  eggert
  40.  * Be able to parse our own date format.  Don't assume year<10000.
  41.  *
  42.  * Revision 5.0  1990/08/22  08:12:40  eggert
  43.  * Switch to GMT and fix the bugs exposed thereby.  Update timezones.
  44.  * Ansify and Posixate.  Fix peekahead and int-size bugs.
  45.  *
  46.  * Revision 1.4  89/05/01  14:48:46  narten
  47.  * fixed #ifdef DEBUG construct
  48.  * 
  49.  * Revision 1.3  88/08/28  14:53:40  eggert
  50.  * Remove unportable "#endif XXX"s.
  51.  * 
  52.  * Revision 1.2  87/03/27  14:21:53  jenkins
  53.  * Port to suns
  54.  * 
  55.  * Revision 1.1  82/05/06  11:38:26  wft
  56.  * Initial revision
  57.  * 
  58.  */
  59.  
  60. #include "rcsbase.h"
  61.  
  62. libId(partId, "$Id: partime.c%v 1.2 1991/08/23 13:31:42 SGP Exp $")
  63.  
  64. #define given(v) (0 <= (v))
  65. #define TMNULL (-1) /* Items not given are given this value */
  66. #define TZ_OFFSET (24*60) /* TMNULL  <  zone_offset - TZ_OFFSET */
  67.  
  68. struct tmwent {
  69.     const char *went;
  70.     short wval;
  71.     char wflgs;
  72.     char wtype;
  73. };
  74.     /* wflgs */
  75. #define TWTIME 02    /* Word is a time value (absence implies date) */
  76. #define TWDST  04    /* Word is a DST-type timezone */
  77.     /* wtype */
  78. #define TM_MON    1    /* month name */
  79. #define TM_WDAY    2    /* weekday name */
  80. #define TM_ZON    3    /* time zone name */
  81. #define TM_LT    4    /* local time */
  82. #define TM_DST    5    /* daylight savings time */
  83. #define TM_12    6    /* AM, PM, NOON, or MIDNIGHT */
  84.     /* wval (for wtype==TM_12) */
  85. #define T12_AM 1
  86. #define T12_PM 2
  87. #define T12_NOON 12
  88. #define T12_MIDNIGHT 0
  89.  
  90. static const struct tmwent tmwords [] = {
  91.     {"january",      0, 0, TM_MON},
  92.     {"february",     1, 0, TM_MON},
  93.     {"march",        2, 0, TM_MON},
  94.     {"april",        3, 0, TM_MON},
  95.     {"may",          4, 0, TM_MON},
  96.     {"june",         5, 0, TM_MON},
  97.     {"july",         6, 0, TM_MON},
  98.     {"august",       7, 0, TM_MON},
  99.     {"september",    8, 0, TM_MON},
  100.     {"october",      9, 0, TM_MON},
  101.     {"november",     10, 0, TM_MON},
  102.     {"december",     11, 0, TM_MON},
  103.  
  104.     {"sunday",       0, 0, TM_WDAY},
  105.     {"monday",       1, 0, TM_WDAY},
  106.     {"tuesday",      2, 0, TM_WDAY},
  107.     {"wednesday",    3, 0, TM_WDAY},
  108.     {"thursday",     4, 0, TM_WDAY},
  109.     {"friday",       5, 0, TM_WDAY},
  110.     {"saturday",     6, 0, TM_WDAY},
  111.  
  112.     {"gmt",          0*60, TWTIME, TM_ZON},   /* Greenwich */
  113.     {"utc",          0*60, TWTIME, TM_ZON},
  114.     {"ut",           0*60, TWTIME, TM_ZON},
  115.  
  116.     {"nzst",        -12*60, TWTIME, TM_ZON},  /* New Zealand */
  117.     {"jst",         -9*60, TWTIME, TM_ZON},   /* Japan */
  118.     {"kst",         -9*60, TWTIME, TM_ZON},   /* Korea */
  119.     {"ist",         -5*60-30, TWTIME, TM_ZON},/* India */
  120.     {"eet",         -2*60, TWTIME, TM_ZON},   /* Eastern Europe */
  121.     {"cet",         -1*60, TWTIME, TM_ZON},   /* Central Europe */
  122.     {"met",         -1*60, TWTIME, TM_ZON},   /* Middle Europe */
  123.     {"wet",          0*60, TWTIME, TM_ZON},   /* Western Europe */
  124.     {"nst",          3*60+30, TWTIME, TM_ZON},/* Newfoundland */
  125.     {"ast",          4*60, TWTIME, TM_ZON},   /* Atlantic */
  126.     {"est",          5*60, TWTIME, TM_ZON},   /* Eastern */
  127.     {"cst",          6*60, TWTIME, TM_ZON},   /* Central */
  128.     {"mst",          7*60, TWTIME, TM_ZON},   /* Mountain */
  129.     {"pst",          8*60, TWTIME, TM_ZON},   /* Pacific */
  130.     {"akst",         9*60, TWTIME, TM_ZON},   /* Alaska */
  131.     {"hast",         10*60, TWTIME, TM_ZON},  /* Hawaii-Aleutian */
  132.     {"hst",          10*60, TWTIME, TM_ZON},  /* Hawaii */
  133.     {"sst",          11*60, TWTIME, TM_ZON},  /* Samoa */
  134.  
  135.     {"nzdt",        -12*60, TWTIME+TWDST, TM_ZON},    /* New Zealand */
  136.     {"kdt",         -9*60, TWTIME+TWDST, TM_ZON},     /* Korea */
  137.     {"bst",          0*60, TWTIME+TWDST, TM_ZON},     /* Britain */
  138.     {"ndt",          2*60+30, TWTIME+TWDST, TM_ZON}, /*Newfoundland (DDST)*/
  139.     {"adt",          4*60, TWTIME+TWDST, TM_ZON},     /* Atlantic */
  140.     {"edt",          5*60, TWTIME+TWDST, TM_ZON},     /* Eastern */
  141.     {"cdt",          6*60, TWTIME+TWDST, TM_ZON},     /* Central */
  142.     {"mdt",          7*60, TWTIME+TWDST, TM_ZON},     /* Mountain */
  143.     {"pdt",          8*60, TWTIME+TWDST, TM_ZON},     /* Pacific */
  144.     {"akdt",         9*60, TWTIME+TWDST, TM_ZON},     /* Alaska */
  145.     {"hadt",         10*60, TWTIME+TWDST, TM_ZON},    /* Hawaii-Aleutian */
  146.  
  147. #if 0
  148.     /*
  149.      * The following names are duplicates or are not well attested.
  150.      * A standard is needed.
  151.      */
  152.     {"?st",         -13*60, TWTIME, TM_ZON},  /* Uelen */
  153.     {"?st",         -11*60, TWTIME, TM_ZON},  /* Magadan */
  154.     {"east",        -10*60, TWTIME, TM_ZON},  /* Eastern Australia */
  155.     {"cast",        -9*60-30, TWTIME, TM_ZON},/* Central Australia */
  156.     {"cst",         -8*60, TWTIME, TM_ZON},   /* China */
  157.     {"hkt",         -8*60, TWTIME, TM_ZON},   /* Hong Kong */
  158.     {"sst",         -8*60, TWTIME, TM_ZON},   /* Singapore */
  159.     {"wast",        -8*60, TWTIME, TM_ZON},   /* Western Australia */
  160.     {"?st",         -7*60, TWTIME, TM_ZON},   /* Novosibirsk */
  161.     {"jt",          -7*60-30, TWTIME, TM_ZON},/* Java */
  162.     {"nst",         -6*60-30, TWTIME, TM_ZON},/* North Sumatra */
  163.     {"?st",         -6*60, TWTIME, TM_ZON},   /* Tashkent */
  164.     {"?st",         -5*60, TWTIME, TM_ZON},      /* Sverdlovsk */
  165.     {"?",           -4*60-30, TWTIME, TM_ZON},/* Afghanistan */
  166.     {"?st",         -4*60, TWTIME, TM_ZON},      /* Rostov */
  167.     {"it",          -3*60-30, TWTIME, TM_ZON},/* Iran */
  168.     {"?st",         -3*60, TWTIME, TM_ZON},   /* Moscow */
  169.     {"ist",         -2*60, TWTIME, TM_ZON},   /* Israel */
  170.     {"ast",          1*60, TWTIME, TM_ZON},   /* Azores */
  171.     {"fst",          2*60, TWTIME, TM_ZON},   /* Fernando de Noronha */
  172.     {"bst",          3*60, TWTIME, TM_ZON},   /* Brazil */
  173.     {"wst",          4*60, TWTIME, TM_ZON},   /* Western Brazil */
  174.     {"ast",          5*60, TWTIME, TM_ZON},   /* Acre Brazil */
  175.     {"?",            9*60+30, TWTIME, TM_ZON},/* Marquesas */
  176.     {"?st",          12*60, TWTIME, TM_ZON},  /* Kwajalein */
  177.  
  178.     {"?dt",         -13*60, TWTIME+TWDST, TM_ZON},      /* Uelen */
  179.     {"?dt",         -11*60, TWTIME+TWDST, TM_ZON},      /* Magadan */
  180.     {"eadt",        -10*60, TWTIME+TWDST, TM_ZON},    /* Eastern Australia */
  181.     {"cadt",        -9*60-30, TWTIME+TWDST, TM_ZON},  /* Central Australia */
  182.     {"cdt",         -8*60, TWTIME+TWDST, TM_ZON},     /* China */
  183.     {"wadt",        -8*60, TWTIME+TWDST, TM_ZON},     /* Western Australia */
  184.     {"?dt",         -7*60, TWTIME+TWDST, TM_ZON},      /* Novosibirsk */
  185.     {"?dt",         -6*60, TWTIME+TWDST, TM_ZON},      /* Tashkent */
  186.     {"?dt",         -5*60, TWTIME+TWDST, TM_ZON},      /* Sverdlovsk */
  187.     {"?dt",         -4*60, TWTIME+TWDST, TM_ZON},      /* Rostov */
  188.     {"?dt",         -3*60, TWTIME+TWDST, TM_ZON},     /* Moscow */
  189.     {"idt",         -2*60, TWTIME+TWDST, TM_ZON},     /* Israel */
  190.     {"eest",        -2*60, TWTIME+TWDST, TM_ZON},     /* Eastern Europe */
  191.     {"cest",        -1*60, TWTIME+TWDST, TM_ZON},     /* Central Europe */
  192.     {"mest",        -1*60, TWTIME+TWDST, TM_ZON},     /* Middle Europe */
  193.     {"west",         0*60, TWTIME+TWDST, TM_ZON},     /* Western Europe */
  194.     {"adt",          1*60, TWTIME+TWDST, TM_ZON},      /* Azores */
  195.     {"fdt",          2*60, TWTIME+TWDST, TM_ZON},     /* Fernando de Noronha */
  196.     {"edt",          3*60, TWTIME+TWDST, TM_ZON},     /* Eastern Brazil */
  197.     {"wdt",          4*60, TWTIME+TWDST, TM_ZON},     /* Western Brazil */
  198.     {"adt",          5*60, TWTIME+TWDST, TM_ZON},     /* Acre Brazil */
  199. #endif
  200.  
  201.     {"lt",           0, TWTIME, TM_LT},       /* local time */
  202.     {"dst",          1*60, TWTIME, TM_DST},      /* daylight savings time */
  203.     {"ddst",         2*60, TWTIME, TM_DST},      /* double dst */
  204.  
  205.     {"am",           T12_AM,    TWTIME, TM_12},
  206.     {"pm",           T12_PM,    TWTIME, TM_12},
  207.     {"noon",         T12_NOON,    TWTIME, TM_12},
  208.     {"midnight",     T12_MIDNIGHT,    TWTIME, TM_12},
  209.  
  210.     {0, 0, 0, 0},             /* Zero entry to terminate searches */
  211. };
  212.  
  213. struct token {
  214.     const char *tcp;/* pointer to string */
  215.     int tcnt;    /* # chars */
  216.     char tbrk;    /* "break" char */
  217.     char tbrkl;    /* last break char */
  218.     char tflg;    /* 0 = alpha, 1 = numeric */
  219.     union {         /* Resulting value; */
  220.         int tnum;/* either a #, or */
  221.         const struct tmwent *ttmw;/* ptr to a tmwent. */
  222.     } tval;
  223. };
  224.  
  225. static const struct tmwent*ptmatchstr P((const char*,int,const struct tmwent*));
  226. static int pt12hack P((struct tm *,int));
  227. static int ptitoken P((struct token *));
  228. static int ptstash P((int *,int));
  229. static int pttoken P((struct token *));
  230.  
  231.     static int
  232. goodzone(register const struct token *t, int offset, int *am)
  233. {
  234.     register int m;
  235.     if (
  236.         t->tflg  &&
  237.         t->tcnt == 4+offset  &&
  238.         (m = t->tval.tnum) <= 2400  &&
  239.         isdigit(t->tcp[offset]) &&
  240.         (m%=100) < 60
  241.     ) {
  242.         m += t->tval.tnum/100 * 60;
  243.         if (t->tcp[offset-1]=='+')
  244.             m = -m;
  245.         *am = m;
  246.         return 1;
  247.     }
  248.     return 0;
  249. }
  250.  
  251.     int
  252. partime(const char *astr, register struct tm *atm, int *zone)
  253. {
  254.     register int i;
  255.     struct token btoken, atoken;
  256.     int zone_offset; /* minutes west of GMT, plus TZ_OFFSET */
  257.     register const char *cp;
  258.     register char ch;
  259.     int ord, midnoon;
  260.     int *atmfield, dst, m;
  261.     int got1 = 0;
  262.  
  263.     atm->tm_sec = TMNULL;
  264.     atm->tm_min = TMNULL;
  265.     atm->tm_hour = TMNULL;
  266.     atm->tm_mday = TMNULL;
  267.     atm->tm_mon = TMNULL;
  268.     atm->tm_year = TMNULL;
  269.     atm->tm_wday = TMNULL;
  270.     atm->tm_yday = TMNULL;
  271.     midnoon = TMNULL;        /* and our own temp stuff */
  272.     zone_offset = TMNULL;
  273.     dst = TMNULL;
  274.     btoken.tcnt = btoken.tbrk = 0;
  275.     btoken.tcp = astr;
  276.  
  277.     for (;; got1=1) {
  278.     if (!ptitoken(&btoken))                /* Get a token */
  279.       {     if(btoken.tval.tnum) return(0);         /* Read error? */
  280.         if (given(midnoon))            /* EOF, wrap up */
  281.             if (!pt12hack(atm, midnoon))
  282.                 return 0;
  283.         if (!given(atm->tm_min))
  284.             atm->tm_min = 0;
  285.         *zone  =
  286.                 (given(zone_offset) ? zone_offset-TZ_OFFSET : 0)
  287.             -    (given(dst) ? dst : 0);
  288.         return got1;
  289.       }
  290.     if(btoken.tflg == 0)        /* Alpha? */
  291.       {     i = btoken.tval.ttmw->wval;
  292.         switch (btoken.tval.ttmw->wtype) {
  293.           default:
  294.             return 0;
  295.           case TM_MON:
  296.             atmfield = &atm->tm_mon;
  297.             break;
  298.           case TM_WDAY:
  299.             atmfield = &atm->tm_wday;
  300.             break;
  301.           case TM_DST:
  302.             atmfield = &dst;
  303.             break;
  304.           case TM_LT:
  305.             if (ptstash(&dst, 0))
  306.                 return 0;
  307.             i = 48*60; /* local time magic number -- see maketime() */
  308.             /* fall into */
  309.           case TM_ZON:
  310.             i += TZ_OFFSET;
  311.             if (btoken.tval.ttmw->wflgs & TWDST)
  312.                 if (ptstash(&dst, 60))
  313.                     return 0;
  314.             /* Peek ahead for offset immediately afterwards. */
  315.             if (
  316.                 (btoken.tbrk=='-' || btoken.tbrk=='+') &&
  317.                 (atoken=btoken, ++atoken.tcnt, ptitoken(&atoken)) &&
  318.                 goodzone(&atoken, 0, &m)
  319.             ) {
  320.                 i += m;
  321.                 btoken = atoken;
  322.             }
  323.             atmfield = &zone_offset;
  324.             break;
  325.           case TM_12:
  326.             atmfield = &midnoon;
  327.         }
  328.         if (ptstash(atmfield, i))
  329.             return(0);        /* ERR: val already set */
  330.         continue;
  331.       }
  332.  
  333.     /* Token is number.  Lots of hairy heuristics. */
  334.     if (!isdigit(*btoken.tcp)) {
  335.         if (!goodzone(&btoken, 1, &m))
  336.             return 0;
  337.         zone_offset = TZ_OFFSET + m;
  338.         continue;
  339.     }
  340.  
  341.     i = btoken.tval.tnum;   /* Value now known to be valid; get it. */
  342.     if (btoken.tcnt == 3)    /*  3 digits = HMM   */
  343.       {
  344. hhmm4:        if (ptstash(&atm->tm_min, i%100))
  345.             return(0);        /* ERR: min conflict */
  346.         i /= 100;
  347. hh2:            if (ptstash(&atm->tm_hour, i))
  348.             return(0);        /* ERR: hour conflict */
  349.         continue;
  350.       }
  351.  
  352.     if (4 < btoken.tcnt)
  353.         goto year4; /* far in the future */
  354.     if(btoken.tcnt == 4)    /* 4 digits = YEAR or HHMM */
  355.       {    if (given(atm->tm_year)) goto hhmm4;    /* Already got yr? */
  356.         if (given(atm->tm_hour)) goto year4;    /* Already got hr? */
  357.         if(btoken.tbrk == ':')            /* HHMM:SS ? */
  358.             if ( ptstash(&atm->tm_hour, i/100)
  359.               || ptstash(&atm->tm_min, i%100))
  360.                 return(0);        /* ERR: hr/min clash */
  361.             else goto coltm2;        /* Go handle SS */
  362.         if(btoken.tbrk != ',' && btoken.tbrk != '/'
  363.           && (atoken=btoken, ptitoken(&atoken))    /* Peek */
  364.           && ( atoken.tflg
  365.              ? !isdigit(*atoken.tcp)
  366.              : atoken.tval.ttmw->wflgs & TWTIME)) /* HHMM-ZON */
  367.             goto hhmm4;
  368.         goto year4;            /* Give up, assume year. */
  369.       }
  370.  
  371.     /* From this point on, assume tcnt == 1 or 2 */
  372.     /* 2 digits = MM, DD, or HH (MM and SS caught at coltime) */
  373.     if(btoken.tbrk == ':')        /* HH:MM[:SS] */
  374.         goto coltime;        /*  must be part of time. */
  375.     if (31 < i)
  376.         return 0;
  377.  
  378.     /* Check for numerical-format date */
  379.     for (cp = "/-."; ch = *cp++;)
  380.       {    ord = (ch == '.' ? 0 : 1);    /* n/m = D/M or M/D */
  381.         if(btoken.tbrk == ch)            /* "NN-" */
  382.           {    if(btoken.tbrkl != ch)
  383.               {
  384.                 atoken = btoken;
  385.                 atoken.tcnt++;
  386.                 if (ptitoken(&atoken)
  387.                   && atoken.tflg == 0
  388.                   && atoken.tval.ttmw->wtype == TM_MON)
  389.                     goto dd2;
  390.                 if(ord)goto mm2; else goto dd2; /* "NN-" */
  391.               }                /* "-NN-" */
  392.             if (!given(atm->tm_mday)
  393.               && given(atm->tm_year))    /* If "YYYY-NN-" */
  394.                 goto mm2;        /* then always MM */
  395.             if(ord)goto dd2; else goto mm2;
  396.           }
  397.         if(btoken.tbrkl == ch            /* "-NN" */
  398.           && given(ord ? atm->tm_mon : atm->tm_mday))
  399.             if (!given(ord ? atm->tm_mday : atm->tm_mon)) /* MM/DD */
  400.                 if(ord)goto dd2; else goto mm2;
  401.       }
  402.  
  403.     /* Now reduced to choice between HH and DD */
  404.     if (given(atm->tm_hour)) goto dd2;    /* Have hour? Assume day. */
  405.     if (given(atm->tm_mday)) goto hh2;    /* Have day? Assume hour. */
  406.     if (given(atm->tm_mon)) goto dd2;    /* Have month? Assume day. */
  407.     if(i > 24) goto dd2;            /* Impossible HH means DD */
  408.     atoken = btoken;
  409.     if (!ptitoken(&atoken))            /* Read ahead! */
  410.         if(atoken.tval.tnum) return(0); /* ERR: bad token */
  411.         else goto dd2;            /* EOF, assume day. */
  412.     if ( atoken.tflg
  413.        ? !isdigit(*atoken.tcp)
  414.        : atoken.tval.ttmw->wflgs & TWTIME)
  415.         /* If next token is a time spec, assume hour */
  416.         goto hh2;        /* e.g. "3 PM", "11-EDT"  */
  417.  
  418. dd2:    if (ptstash(&atm->tm_mday, i))    /* Store day (1 based) */
  419.         return(0);
  420.     continue;
  421.  
  422. mm2:    if (ptstash(&atm->tm_mon, i-1))    /* Store month (make zero based) */
  423.         return(0);
  424.     continue;
  425.  
  426. year4:    if ((i-=1900) < 0  ||  ptstash(&atm->tm_year, i)) /* Store year-1900 */
  427.         return(0);        /* ERR: year conflict */
  428.     continue;
  429.  
  430.     /* Hack HH:MM[[:]SS] */
  431. coltime:
  432.     if (ptstash(&atm->tm_hour, i)) return 0;
  433.     if (!ptitoken(&btoken))
  434.         return(!btoken.tval.tnum);
  435.     if(!btoken.tflg) return(0);    /* ERR: HH:<alpha> */
  436.     if(btoken.tcnt == 4)        /* MMSS */
  437.         if (ptstash(&atm->tm_min, btoken.tval.tnum/100)
  438.           || ptstash(&atm->tm_sec, btoken.tval.tnum%100))
  439.             return(0);
  440.         else continue;
  441.     if(btoken.tcnt != 2
  442.       || ptstash(&atm->tm_min, btoken.tval.tnum))
  443.         return(0);        /* ERR: MM bad */
  444.     if (btoken.tbrk != ':') continue;    /* Seconds follow? */
  445. coltm2:    if (!ptitoken(&btoken))
  446.         return(!btoken.tval.tnum);
  447.     if(!btoken.tflg || btoken.tcnt != 2    /* Verify SS */
  448.       || ptstash(&atm->tm_sec, btoken.tval.tnum))
  449.         return(0);        /* ERR: SS bad */
  450.     }
  451. }
  452.  
  453. /* Store date/time value, return 0 if successful.
  454.  * Fail if entry is already set.
  455.  */
  456.     static int
  457. ptstash(int *adr,int val)
  458. {    register int *a;
  459.     if (given(*(a=adr)))
  460.         return 1;
  461.     *a = val;
  462.     return(0);
  463. }
  464.  
  465. /* This subroutine is invoked for AM, PM, NOON and MIDNIGHT when wrapping up
  466.  * just prior to returning from partime.
  467.  */
  468.     static int
  469. pt12hack(register struct tm *tm, register int aval)
  470. {    register int h = tm->tm_hour;
  471.     switch (aval) {
  472.       case T12_AM:
  473.       case T12_PM:
  474.         if (h > 12)
  475.             return 0;
  476.         if (h == 12)
  477.             tm->tm_hour = 0;
  478.         if (aval == T12_PM)
  479.             tm->tm_hour += 12;
  480.         break;
  481.       default:
  482.         if (0 < tm->tm_min  ||  0 < tm->tm_sec)
  483.             return 0;
  484.         if (!given(h) || h==12)
  485.             tm->tm_hour = aval;
  486.         else if (aval==T12_MIDNIGHT  &&  (h==0 || h==24))
  487.             return 0;
  488.     }
  489.     return 1;
  490. }
  491.  
  492. /* Get a token and identify it to some degree.
  493.  * Returns 0 on failure; token.tval will be 0 for normal EOF, otherwise
  494.  * hit error of some sort
  495.  */
  496.  
  497.     static int
  498. ptitoken(register struct token *tkp)
  499. {
  500.     register const char *cp;
  501.     register int i, j, k;
  502.  
  503.     if (!pttoken(tkp))
  504. #ifdef DEBUG
  505.         {
  506.         VOID printf("EOF\n");
  507.         return(0);
  508.         }
  509. #else
  510.         return(0);
  511. #endif    
  512.     cp = tkp->tcp;
  513.  
  514. #ifdef DEBUG
  515.     VOID printf("Token: \"%.*s\" ", tkp->tcnt, cp);
  516. #endif
  517.  
  518.     if (tkp->tflg) {
  519.         i = tkp->tcnt;
  520.         if (*cp == '+' || *cp == '-') {
  521.             cp++;
  522.             i--;
  523.         }
  524.         while (0 <= --i) {
  525.             j = tkp->tval.tnum*10;
  526.             k = j + (*cp++ - '0');
  527.             if (j/10 != tkp->tval.tnum  ||  k < j) {
  528.                 /* arithmetic overflow */
  529.                 tkp->tval.tnum = 1;
  530.                 return 0;
  531.             }
  532.             tkp->tval.tnum = k;
  533.         }
  534.     } else if (!(tkp->tval.ttmw  =  ptmatchstr(cp, tkp->tcnt, tmwords)))
  535.       {
  536. #ifdef DEBUG
  537.         VOID printf("Not found!\n");
  538. #endif
  539.         tkp->tval.tnum = 1;
  540.         return 0;
  541.       }
  542.  
  543. #ifdef DEBUG
  544.     if(tkp->tflg)
  545.         VOID printf("Val: %d.\n",tkp->tval.tnum);
  546.     else VOID printf("Found: \"%s\", val: %d, type %d\n",
  547.         tkp->tval.ttmw->went,tkp->tval.ttmw->wval,tkp->tval.ttmw->wtype);
  548. #endif
  549.  
  550.     return(1);
  551. }
  552.  
  553. /* Read token from input string into token structure */
  554.     static int
  555. pttoken(register struct token *tkp)
  556. {
  557.     register const char *cp;
  558.     register int c;
  559.     const char *astr;
  560.  
  561.     tkp->tcp = astr = cp = tkp->tcp + tkp->tcnt;
  562.     tkp->tbrkl = tkp->tbrk;        /* Set "last break" */
  563.     tkp->tcnt = tkp->tbrk = tkp->tflg = 0;
  564.     tkp->tval.tnum = 0;
  565.  
  566.     while(c = *cp++)
  567.       {    switch(c)
  568.           {    case ' ': case '\t':    /* Flush all whitespace */
  569.             case '\r': case '\n':
  570.             case '\v': case '\f':
  571.                 if (!tkp->tcnt) {    /* If no token yet */
  572.                     tkp->tcp = cp;    /* ignore the brk */
  573.                     continue;    /* and go on. */
  574.                 }
  575.                 /* fall into */
  576.             case '(': case ')':    /* Perhaps any non-alphanum */
  577.             case '-': case ',':    /* shd qualify as break? */
  578.             case '+':
  579.             case '/': case ':': case '.':    /* Break chars */
  580.                 if(tkp->tcnt == 0)    /* If no token yet */
  581.                   {    tkp->tcp = cp;    /* ignore the brk */
  582.                     tkp->tbrkl = c;
  583.                       continue;    /* and go on. */
  584.                   }
  585.                 tkp->tbrk = c;
  586.                 return(tkp->tcnt);
  587.           }
  588.         if (!tkp->tcnt++) {        /* If first char of token, */
  589.             if (isdigit(c)) {
  590.                 tkp->tflg = 1;
  591.                 if (astr<cp-2 && (cp[-2]=='-'||cp[-2]=='+')) {
  592.                     /* timezone is break+sign+digit */
  593.                     tkp->tcp--;
  594.                     tkp->tcnt++;
  595.                 }
  596.             }
  597.         } else if ((isdigit(c)!=0) != tkp->tflg) { /* else check type */
  598.             tkp->tbrk = c;
  599.             return --tkp->tcnt;    /* Wrong type, back up */
  600.         }
  601.       }
  602.     return(tkp->tcnt);        /* When hit EOF */
  603. }
  604.  
  605.  
  606.     static const struct tmwent *
  607. ptmatchstr(const char *astr,int cnt,const struct tmwent *astruc)
  608. {
  609.     register const char *cp, *mp;
  610.     register int c;
  611.     const struct tmwent *lastptr;
  612.     int i;
  613.  
  614.     lastptr = 0;
  615.     for(;mp = astruc->went; astruc += 1)
  616.       {    cp = astr;
  617.         for(i = cnt; i > 0; i--)
  618.           {
  619.             switch (*cp++ - (c = *mp++))
  620.               {    case 0: continue;    /* Exact match */
  621.                 case 'A'-'a':
  622.                     if (ctab[c] == Letter)
  623.                     continue;
  624.               }
  625.             break;
  626.           }
  627.         if(i==0)
  628.             if (!*mp) return astruc;    /* Exact match */
  629.             else if(lastptr) return(0);    /* Ambiguous */
  630.             else lastptr = astruc;        /* 1st ambig */
  631.       }
  632.     return lastptr;
  633. }
  634.